Building a Release Workflow
Understand how to build and trigger a release automation workflow.
We'll cover the following
In this lesson, we will take the manual, toilsome process of publishing a new release and transform it into GitHub workflow automation triggered by pushing a tag to the repository. This automation will result in a GitHub release containing build notes and release artifacts for a tagged, semantic version of the tweeter command-line tool. Automating manual processes such as releases reduces the possibility of manual errors and increases the productivity of project maintainers.
In this lesson, we will learn how to create a release automation workflow. We will learn how to trigger an automation to run after the successful completion of dependent automation. We will learn how to build binaries targeting multiple platforms. Finally, we will automate the creation of a GitHub release, including automatically generated release notes.
GitHub releases#
GitHub releases are deployable software iterations for a repository that are based on Git tags. A release declares to the world that a new version of the software is available. A release is composed of a title, an optional description, and an optional set of artifacts. The title provides a name for the release. The description is used to provide insight into what is contained in the release – for example, what new features or pull requests were included in the release, and which GitHub contributors contributed to the release.
The description is formatted in GitHub Markdown. Release artifacts are files associated with the release that users can download – for example, a command-line application might publish compiled binaries ready for download and use.
Git tags#
A Git tag is a named pointer to a specific reference in the Git repository and is often formatted as semantic versions, such as v1.2.3. Semantic versioning is a convention for naming tags that provides some insight into the significance of a new release. A semantic version tag is formatted as Major.Minor.Patch. The following behavior is expressed by incrementing the individual field:
Major: Increment when incompatible API changes occur, such as breaking changes.Minor: Increment when functionality is added in a backward-compatible manner, such as new features.Patch: Increment when making backward-compatible bug fixes.
Release automation for tweeter#
We will build upon the CI automation and add release automation for tweeter.
Goals for automation#
In our release automation, we are going to accomplish the following goals:
Trigger automation when the repository is tagged with a semantic version.
Run unit tests and validation prior to creating the release.
Inject the semantic version of the release into the tweeter application.
Build cross-platform versions of the tweeter application.
Generate release notes from the pull requests in the release.
Tag the contributors in the release.
Create a GitHub release containing the following:
A title containing the semantic version of the release.
A description containing the generated release notes.
Artifacts consisting of the cross-platform binaries.
Next, we will create release automation to satisfy these requirements.
Creating the release automation#
With our goals for the tweeter release automation specified, we are ready to extend the existing continuous integration workflow that we built in the previous section and add a release job to achieve those goals. The release job is longer than the continuous integration workflow, so we'll approach it one piece at a time.
Triggering the automation#
The first goal for the tweeter release workflow is triggering the automation when the repository is tagged with a semantic version:
The preceding snippet of YAML is unchanged from the continuous integration workflow. It will trigger the workflow with any tag matching the semantic version in the form of v1.2.3. However, the workflow will also trigger on pull requests and pushes. We want the continuous integration workflow to execute on pull requests and pushes, but we do not want to execute a release each time. We will need to restrict execution of the release job to only when executing on a tag push.
Restricting release execution#
The first and second goal for the tweeter release workflow is as follows:
Triggering the automation when the repository is tagged with a semantic version
Running unit tests and validation prior to creating the release
Let's make sure the release job only executes when the repository is tagged:
The preceding job definition completes the first goal of only running the release when a tag starting with v is pushed by specifying an if statement to verify that the github.ref context variable starts with refs/tags/v. The second goal of ensuring the test job executes successfully before attempting to execute the release job is achieved by specifying needs: test. If needs: test was not specified on the release job, both jobs will execute concurrently, which can cause a release to be created without passing validation.
Workspace and environmental setup#
To achieve the rest of the automation goals, we will need to set up the workspace:
The preceding code does the following:
Checks out the source at the Git ref associated with the tag.
Creates a
RELEASE_VERSIONenvironment variable with the tag, such asv1.2.3.Installs Go 1.17 tools.
Building cross-platform binaries and version injection#
The third and fourth goals of the tweeter release flow are as follows:
Inject the semantic version of the release into the tweeter application.
Build cross-platform versions of the tweeter application
Let's get started by injecting the semantic version of the release into the compiled binary:
The preceding steps do the following:
Install the
goxcommand-line tool for simplifying Go cross-compilation.Build cross-platform binaries for each specified platform/architecture while injecting the
RELEASE_VERSIONenvironment variable into a Goldflag. Theldflag -Xreplaces the default value of the Version variable in thegithub.com/devopsforgo/github-actions/pkg/tweeterpackage with the semantic version tag of the build. The output ofgoxis structured byOUTPUT_PATH_FORMAT– for example, the output directory looks like the following:
One of the most compelling reasons to use Golang for building applications is the relative ease of building cross-platform, statically linked binaries. With a couple of steps, we can build versions of tweeter for Linux, Windows, macOS targeting AMD64 and ARM64, as well as many other platforms and architectures. These small, statically linked binaries are simple to distribute and execute across platforms and architectures.
With the preceding steps, the release job has compiled the semantic version of the release into the platform and architecture-specific, statically linked binaries. In the next step, we will use the semantic version to generate release notes.
Generating release notes#
We have the following goals associated with generating release notes:
Generate release notes from the pull requests in the release.
Tag the contributors in the release.
Create a GitHub release containing the following:
A description containing the generated release notes.
Here's some great news! With a bit of configuration and tagging, release note generation is automatically handled by GitHub. We'll start by adding a new file to the repository, ./.github/release.yml, with the following content:
The preceding release configuration will tell GitHub to filter and categorize pull requests based on the applied labels. For example, pull requests labeled with ignore-for-release will be excluded from the release notes, but a pull request labeled with enhancement will be grouped under the New Features header in the release notes:
The preceding step generates release notes. The step executes an API call to the GitHub API to generate the release notes for the given tag. The command captures the JSON body of the response in a tmp-release-notes.json filename. Note that gh requires a GitHub token to interact with the GitHub APIs. The GitHub secret is passed into the GITHUB_TOKEN environment variable and is used by gh to authenticate.
The following is an example of JSON returned from the generate-notes API call:
We will use tmp-release-notes.json to create the release in the next step.
Creating the GitHub release#
The final goal of creating the release automation is as follows:
A title containing the semantic version of the release.
A description containing the generated release notes.
Artifacts consisting of the cross-platform binaries.
Let's get started creating our release automation:
The preceding steps do the following:
Execute
tarandgzipon the binaries. With Go 1.17, tweeter bins are roughly 6.5 MB. Aftergzip, each artifact is less than 4 MB.Create a GitHub release using the
ghcommand-line tool, which is available on all GitHub job executors.ghrequires a GitHub token to interact with the GitHub APIs. The GitHub secret is passed into theGITHUB_TOKENenvironment variable and is used byghto authenticate.gh release createcreates a release and uploads each of the files specified after the arguments. Each file uploaded becomes an artifact on the release. Note#after each artifact file path. The text after#is the name that the artifact will display, as in the GitHub UI. We also specify the title and the release notes using the capturedtmp-release-notes.jsonandjqto parse and select the JSON content.
At this point, we have a created release targeting multiple platforms and architectures, satisfying all our goals for automation. Let's kick off a release and see the results.
Creating a release of tweeter#
We need to first clone our Github repository using the commands below. Replace github_repo with the link to your GitHub repository and repository_name with the name of your repository.
We then need to replace the .github/workflows/tweeter-automation.yaml file in our cloned directory. Run these commands in the terminal above and then paste the contents of the new file provided just after. You need to then press “Return” and then “Ctrl+C.”
Now that we have built a release job that will automate the releases of tweeter, we can now tag the repository and release a version of the application.
To start the release automation, we are going to create and push the v0.0.1 tag to the repository by executing the following in the terminal above:
You will have to fill the commands with your own details, as follows:
github_email: The email of your GitHub account.github_user: The username of your GitHub account.
In the execution of the final command, you will have to input your GitHub username and access token.
After the tag is pushed, we should be able to go to the Actions tab on our GitHub repository and see the tag workflow executing. If we navigate to the workflow, we should see something like the following:
As we can see in the preceding figure, the tests have been executed and subsequently, the release job has been too. If we navigate to the release job, we should see something like the following:
As we can see in the preceding figure, the release job has successfully executed each of the steps, and the release was created. If we go to the landing page of the repository, we should see that a new release has been created.
With the preceding steps, we have satisfied the goals of our release automation job. We triggered the release job after the tests were executed to ensure a release will always pass our validations before being published. We built statically linked binaries for each of the specified platform/architecture combinations using gox. We leveraged GitHub release notes autogeneration to create beautifully formatted release notes. And finally, we created a release with the generated notes and artifacts from the build.
In this example, we learned to build a release automation job for a Go project, but any language and set of tools can be used in a similar manner to create release automation for any language.
We have no more manual toil to release the tweeter project. All that needs to be done is to push a tag to the repository. Our use of open-source actions has enhanced our ability to author this automation.
Building a Continuous Integration Workflow
Creating a Custom GitHub Action Using Go